<template>
  <div class="container">
    <div class="header">
      <h1>智能会议纪要系统</h1>
      <p>录音 → 转写 → 自动生成会议摘要</p>
    </div>
    <div class="control-panel">
      <div style="display: flex;">
        <el-button class="button button-primary" :class="{ 'recording': state.recording }" type="primary"
          id="startRecord" @click="startRecord">
          🎤 开始录音
          <span class="status-indicator"></span>
        </el-button>
        <el-button class="button button-primary" id="stopRecord" :disabled="!state.recording" @click="endRecord">
          ⏹️ 停止录音
        </el-button>

      </div>

      <div style="display: flex;">

        <el-upload ref="uploadRef" v-model:file-list="fileState.fileList" accept=".aac,.mp3,.wav" :before-upload="fileBeforeUpload"
          :limit="1" :on-error="filefuiled" :on-success="filefuiled" name="audio_file" :show-file-list="false"
          action="/asr?encode=true&task=transcribe&output=txt" :on-exceed="handleExceed" :headers="{ 'Accept': 'application/json' }"
          :data="{ encode: true, task: 'transcribe', output: 'json' }" :auto-upload="true">
          <el-button class="button button-primary" :loading="fileState.loading" :disabled="fileState.loading">
            📤
            <span></span>上传文件并处理</el-button>
        </el-upload>
        <el-button class="button" type="primary" style="margin-left: 10px;" @click="reset">重置</el-button>
      </div>
    </div>
    <div class="content-wrapper">
      <div class="card">
        <div style="display: flex;width: 100%;justify-content: space-between;align-items: center;">
          <h2>逐字稿</h2>
          <el-button type="primary" style="margin: 0 10px;" @click="generateMeetingSummary">生成纪要</el-button>
        </div>
        <el-input v-loading="fileState.loading" v-model="fileState.videoTex" class="loading" type="textarea" rows="12"
          placeholder="录音转写内容将显示在这里..."></el-input>
      </div>
      <div style="width: 20px;"></div>
      <div class="card">
        <div style="display: flex;align-items: center;">
          <el-image :src="summaryImage" style=" height: 50px" />
          <div v-loading="audioState.loading">
            <img alt="阅读" v-loading="audioState.loading" @click="read" style="opacity: 0.6;"
              src=""
              width="24" height="24" class="volume-btn audio-volume-default"
              :class="{ 'audio_playing': audioState.playing }">
          </div>
          <audio ref="audioRef" style="margin-left: 20px;" :src="audioState.url"></audio>
        </div>
        <el-input class="loading" v-loading='state.loading' rows="12" v-model="fileState.meetingSummary" type="textarea"
          placeholder="生成的会议摘要将显示在这里..."></el-input>
      </div>
    </div>
    <div style="padding-bottom: 10px;">

      <div class="canviseinit">
        <canvas class="showCanvasVideo" id="canvas"></canvas>
        <div id="progressBarWarp" class="progress-bar">
          <div id="progressBar"></div>
        </div>
        <div class="time">{{ videoState.duration }}s</div>
        <div style="display:flex;width: 80px;">
          <el-button v-if="!state.recording" plain circle
            :icon="state.play == false || state.play == 'pause' ? VideoPlay : VideoPause" @click="playRecord">
          </el-button>
          <el-button v-if="!state.recording" plain style="margin-right: 10px;" @click="stopPlay" :icon="SwitchButton"
            circle></el-button>
        </div>
      </div>
      <div style="display:flex;margin-top: 10px;justify-content: space-between;">
        <div></div>
        <div>
          <el-button plain type="primary" @click="downloadVideoWAV" :icon="Download">下载WAV</el-button>
          <el-button plain type="primary" @click="downloadVideoMP3" :icon="Download">下载MP3</el-button>
          <el-button plain type="primary" @click="uploadVideo" :icon="UploadFilled" :loading="fileState.loading"
            :disabled="fileState.loading">上传录音文件</el-button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { VideoPlay, VideoPause, SwitchButton, Download, UploadFilled } from '@element-plus/icons-vue'
import { videoFun } from './video.js'
import { uploadFile } from './uploadFile.js'
import { reactive, ref, onMounted, watchEffect } from 'vue'
import { genFileId,ElMessage } from 'element-plus'
import summaryImage from './lib/ai-summary-title.ace9b518.png'
import axios from 'axios'
// import fs from 'fs'

let progressInterval = null
const uploadRef=ref(null)
let state = reactive({
  recording: false,
  play: false,
  loading: false
})
let fileState = reactive({
  fileList: [],
  loading: false,
  meetingSummary: '会议强调以白沙为基础，开发生态合法综合平台，并推动相关产品应用和奖项申报。需注意项目后续研发与知识产权的保护，同时研究创新管家服务模式及其边界问题，以缩小与当前技术如大数据、大模型等间的差距。',
  videoTex: `要以白沙为基础,要研究生态合法综合平台的用途。
              除了推动奖项,还要推动这些产品的算一算。
              我们要做好后续办,包括采锐,给环境影响这个项目的练修。
              要做好后续的研发项目申报,要做好奖项申报的。
              现在做的过程中,就要瞄准这个方向去。
              所以这一关中,知识产权必须要弄。
              要去研究界限。
              我们在管家服务的模式上创新。
              现在我们几个管家呢,唐山在做,成德在做,湖山邮田在做,海南新模式。
              上午讲,你说大数据,人家大模型都搞那么深度了,那么强了,我们还在人在那边去巡合呢。
              我们的乌鲁基,乌鲁团,躺在最大吊了。`

})

const { upFileState, onUploadFile } = uploadFile()
const { startVideoRecord,
  endVideoRecord,
  stopVideoPlay,
  playVideoRecord,
  downloadVideoPCM, pauseVideoPlay,
  resumeVideoPlay, downloadVideoMP3,
  downloadVideoWAV, videoState, getVideoWAV } = videoFun()


const audioState = reactive({
  loading: false,
  url: 'http://10.0.116.193:9966/static/wavs/020539_use15.29s-audio0s-seed1031.pt-te0.1-tp0.7-tk20-textlen96-57351-merge.wav',
  playing: false,
})
const audioRef = ref(null)
const read = () => {
  if (audioState.url) {
    audioRef.value.play()
    audioState.playing = true
    return
  }

  const formdata = new FormData()
  formdata.append('text', fileState.meetingSummary)
  formdata.append('prompt', '[break_6]')
  formdata.append('voice', '1031.pt')
  formdata.append('speed', 5)
  formdata.append('temperature', 0.1)
  formdata.append('top_p', 0.70)
  formdata.append('top_k', 20)
  formdata.append('refine_max_new_token', 384)
  formdata.append('infer_max_new_token', 2048)
  formdata.append('text_seed', 42)
  formdata.append('skip_refine', 1)
  formdata.append('custom_voice', 0)
  audioState.loading = true
  axios.request({
    url: '/tts',
    method: 'post', //
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    },
    data: formdata
  }).then((res) => {
    audioState.loading = false
    if (res.data.code == 0) {
      audioState.url = res.data.url
      setTimeout(() => {
        audioRef.value.play()
        audioState.playing = true
      })
    }
  })

}
onMounted(() => {
  audioRef.value.addEventListener('play', () => {
    audioState.playing = true
  })
  audioRef.value.addEventListener('pause', () => {
    audioState.playing = false
  })
  audioRef.value.addEventListener('ended', () => {
    audioState.playing = false
  })
})
const fileBeforeUpload = (file) => {
  let filename = file.name.split('.')
  if (!['aac', 'mp3', 'wav'].includes(filename.pop())) {
    ElMessage.error('请上传正确的音频格式文件，支持.aac/.mp3/.wav文件')
 
    return false
  }
  fileState.loading = true
  return true
}
const handleExceed = (files) => {
  uploadRef.value.clearFiles()
  const file = files[0] 
  file.uid = genFileId()
  uploadRef.value.handleStart(file)
  uploadRef.value.submit(file)
}
const filefuiled = (response) => {
  console.log(response)
  fileState.loading = false
  fileState.videoTex = response
}
const reset = () => {
  fileState.fileList = []
  fileState.loading = false
  fileState.meetingSummary = ''
  fileState.videoTex = ''

  audioState.url = ''
  audioState.playing = false
  audioRef.value.pause()
}

const generateMeetingSummary = async () => {
  state.loading = true

  audioState.url = ''
  audioState.playing = false
  audioRef.value.pause()

  try {

    let response = await fetch('/api/chat', {
      method: 'post',
      responseType: 'stream',
      headers: {
        'Content-Type': 'application/json;charset=utf-8'
      },
      body: JSON.stringify({
        "model": "qwen2.5:7b",
        "stream": true,
        "messages": [
          { "role": "system", "content": "你现在是一名与会人员，需要您根据会议内容，撰写会议纪要，请扮演好这个角色。" },
          { "role": "user", "content": `根据下面描述生成一个段会议纪要：${fileState.videoTex}` },
        ]
      })
    })
    state.loading = false
    fileState.meetingSummary = ''
    if (!response.ok) {
      throw new Error("Network response was not ok")
    }
    // 用来获取一个可读的流的读取器（Reader）以流的方式处理响应体数据
    const reader = response.body.getReader()
    // 将流中的字节数据解码为文本字符串
    const textDecoder = new TextDecoder()
    let result = true
    let sqlValue = ''
    while (result) {
      // done表示流是否已经完成读取  value包含读取到的数据块
      const { done, value } = await reader.read()
      if (done) {
        result = false
        break
      }
      // 拿到的value就是后端分段返回的数据，大多是以data:开头的字符串
      // 需要通过decode方法处理数据块，例如转换为文本或进行其他操作
      const chunkText = textDecoder
        .decode(value)
        .split("\n")
        .forEach((val) => {
          if (!val) return
          try {
            // 后端返回的流式数据一般都是以data:开头的字符，排除掉data:后就是需要的数据
            // 具体返回结构可以跟后端约定
            let text = val?.replace("data:", "") || ""
            text = JSON.parse(text)
            fileState.meetingSummary += text.message.content
            sqlValue += text
          } catch (err) { }
        })
    }
  } catch (err) {

  }
}

const uploadVideo = () => {
  // /asr?encode=true&task=transcribe&output=txt
  fileState.loading = true
  let blob = getVideoWAV()
  const formdata = new FormData()
  formdata.append('audio_file', blob)
  axios.request({
    url: '/asr?encode=true&task=transcribe&output=txt',
    method: 'post', //
    headers: { 'Accept': 'application/json', 'Content-Type': 'multipart/form-data' },
    data: formdata
  }).then((response) => {
    fileState.loading = false
    fileState.videoTex = response.data
  })

}
const startRecord = () => {
  startVideoRecord()
  state.recording = true
  simulateLoadingProgress()
}
const playRecord = () => {
  if (state.play == false) {
    state.play = 'play'
    playVideoRecord()
  }
  else if (state.play == 'play') {
    pausePlay()
    state.play = 'pause'
  }
  else if (state.play == 'pause') {
    state.play = 'play'
    resumeVideoPlay()
  }

}
const stopPlay = () => {
  state.play == false
  stopVideoPlay()
}
const pausePlay = () => {
  state.play = 'pause'
  pauseVideoPlay()
}
const endRecord = () => {
  endVideoRecord()
  state.recording = false
  stopProgress()
  fullProgress()
}
const simulateLoadingProgress = () => {
  const progressBar = document.getElementById("progressBar")
  const MaxWidth = document.getElementById("progressBarWarp").offsetWidth

  let width = 0
  let fn = () => {
    progressInterval = setTimeout(() => {
      if (width >= MaxWidth) {
        progressBar.style.width = MaxWidth + 'px'

      } else {
        width += 1
        progressBar.style.width = width + 'px'
        fn()
      }
    }, 200)
  }
  fn()
}
const fullProgress = () => {
  const progressBar = document.getElementById("progressBar")
  progressBar.style.width = '100%'
}
const stopProgress = () => {
  progressInterval && clearTimeout(progressInterval)
}

</script>

<style lang="scss" scoped>
.warp {
  height: 100%;
}

.w-full {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  height: 100%;

}

body {
  background: var(--background);
  min-height: 100vh;
  padding: 2rem;
}

.container {
  max-width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 0 20px;
  height: 100%;
  overflow: hidden;
}

.header {
  text-align: center;
  margin-bottom: 20px;
}

.header h1 {
  font-size: 2.5rem;
  color: #1e293b;
  margin-bottom: 0.5rem;
}

.header p {
  color: #64748b;
}

.control-panel {
  border-radius: 1rem;
  padding: 15px 15px;
  box-shadow: var(--el-box-shadow-light);
  margin-bottom: 2rem;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.button {
  border: none;
  padding: 15px 20px;
  line-height: 35px;
  border-radius: 0.5rem;
  cursor: pointer;
  transition: all 0.2s;
  font-weight: 500;
  display: flex;
  align-items: center;
  height: 40px;
}

.button-primary {
  background: linear-gradient(to right, #2563eb, #3b82f6);
  color: white;
}

.button-primary:hover {
  opacity: 0.9;
  transform: translateY(-1px);
}

.button-primary:disabled {
  background: #94a3b8;
  cursor: not-allowed;
  opacity: 0.7;
  transform: none;
}

.status-indicator {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #94a3b8;
  margin-left: 0.5rem;
}

.recording .status-indicator {
  background: #ef4444;
  animation: pulse 1.5s infinite;
}

.content-wrapper {
  display: flex;
  align-items: center;
  flex-grow: 2;
}

.card {
  border-radius: 1rem;
  padding: 15px;
  box-shadow: var(--el-box-shadow-light);
  min-height: 300px;
  flex-grow: 1;
  height: 100%;
  box-sizing: border-box;
}

.card h2 {
  color: #1e293b;
  margin-bottom: 1rem;
  font-size: 1.25rem;
}

.transcript {
  background: #f8fafc;
  border-radius: 0.5rem;
  padding: 1rem;
  height: 300px;
  overflow-y: auto;
  line-height: 1.6;
  color: #475569;
  border: 1px solid #e2e8f0;
}

.loading {
  color: #64748b;
  font-style: italic;
  padding: 10;
  text-align: center;
  box-sizing: border-box;
  width: 100%;
  height: 100%;
}

::v-deep .el-textarea__inner {
  background-color: #f8fafc;
  border-radius: 10px;
  height: calc(100% - 65px);
}

@keyframes pulse {
  0% {
    opacity: 1;
  }

  50% {
    opacity: 0.5;
  }

  100% {
    opacity: 1;
  }
}

@media (max-width: 768px) {
  body {
    padding: 1rem;
  }

  .control-panel {
    flex-direction: column;

  }
}

.canviseinit {
  width: 100%;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  margin-top: 20px;
  box-shadow: var(--el-box-shadow-light);
  border-radius: 8px;
  color: #333;
  padding: 0 10px 0 0;
  overflow: hidden;

  .time {
    padding: 0 20px;
  }
}

.showCanvasVideo {
  width: 50px;
  height: 50px;
  margin: 4px;
  border-radius: 8px;
  background: rgba(59, 130, 246, 0.9);
}

.progress-bar {

  height: 6px;
  width: 10px;
  border: 3px;
  margin-left: 20px;
  transition: all 0.2;
  flex-grow: 1;

  div {
    border-radius: 3px;
    background: rgba(64, 158, 255, 0.9);
    height: 100%;
  }
}

.audio_playing {
  animation: myfirst 0.5s;
  animation-direction: reverse;
  animation-iteration-count: infinite;

  -webkit-animation: myfirst 0.5s;
  -webkit-animation-direction: reverse;
  -webkit-animation-iteration-count: infinite;
  /*Safari and Chrome*/
}

@keyframes myfirst {
  0% {
    opacity: 1;
  }

  50% {
    opacity: 0.25;
  }

  100% {
    opacity: 1;
  }
}
</style>